/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.data.values;

import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;

import edu.ucsd.db.bassarisk.common.BassariskInternalError;
import edu.ucsd.db.bassarisk.data.api.IValueType;
import edu.ucsd.db.bassarisk.data.api.IValueType.IReferenceSort.ReferenceSortOption;

/*
 * ::EDIT WARNING:: This file is extremely similar to LongValueType.java in the
 * same package. Bug fixes (improvements) should be propagated to both files if
 * needed (possible).
 * 
 * The replication between these files is a necessary evil to reduce code
 * duplication elsewhere while maintaining high performance.
 * 
 * Thanks!
 */
public class IntType implements IValueType {
  private static final long serialVersionUID = -4803807698441663299L;

  public static class BufferedIntegerReference implements
      IBufferedValueReference<IntType> {
    private ByteBuffer buffer;
    private int length;
    private int startOffset;

    @Override
    public ByteBuffer getBuffer() {
      return this.buffer;
    }

    @Override
    public int getLength() {
      return this.length;
    }

    @Override
    public int getStartOffset() {
      return this.startOffset;
    }

    @Override
    public IValueType getType() {
      return IntType.INSTANCE;
    }

    @Override
    public void reset(final ByteBuffer newBuffer, final int newOffset,
                      final int newLength) {
      this.buffer = newBuffer;
      this.startOffset = newOffset;
      this.length = newLength;
    }

    @Override
    public void resetDefault() {
      this.buffer.putInt(this.startOffset, 0);
    }

    @Override
    public void setBuffer(final ByteBuffer newBuffer) {
      this.buffer = newBuffer;
    }

    @Override
    public void setLength(final int newLength) {
      assert newLength == 8;
    }

    @Override
    public void setStartOffset(final int newOffset) {
      this.startOffset = newOffset;
    }

    @Override
    public String toString() {
      return "Buffered 32-bit Int: " + this.buffer.getInt(this.startOffset);
    }
  }

  public static class IntegerReferenceSort implements IReferenceSort {
    private final ReferenceSortOption option;

    public IntegerReferenceSort(final ReferenceSortOption option) {
      this.option = option;
    }

    @Override
    public IValueType getReferenceType() {
      return IntType.INSTANCE;
    }

    @Override
    public ReferenceSortOption getSortOption() {
      return this.option;
    }

    @Override
    public ITypeTools<?> getTypeTools() {
      return IntTools.INSTANCE;
    }
  }

  public static interface IntegerValueCodec extends IValueCodec<IntType> {
    public int getIntegerValue(final IValueReference<IntType> valueSource);

    public void setIntegerValue(final int value,
                                final IValueReference<IntType> valueDestination);
  };

  public static class IntTools implements ITypeTools<IntType> {
    public static final IntTools INSTANCE = new IntTools();

    private final IntegerValueCodec bufferedCodec;
    private final Comparator<IValueReference<IntType>> bufferedComparator;
    private final IValueCopier<IntType> bufferedCopier;

    private final IntegerValueCodec genericCodec;
    private final Comparator<IValueReference<IntType>> genericComparator;
    private final IValueCopier<IntType> genericCopier;

    private final IntegerValueCodec objectCodec;
    private final Comparator<IValueReference<IntType>> objectComparator;
    private final IValueCopier<IntType> objectCopier;

    protected IntTools() {

      this.genericComparator = new Comparator<IValueReference<IntType>>() {
        @Override
        public int compare(final IValueReference<IntType> g1,
                           final IValueReference<IntType> g2) {
          int v1 = IntTools.this.genericCodec.getIntegerValue(g1);
          int v2 = IntTools.this.genericCodec.getIntegerValue(g2);
          return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
        }
      };

      this.objectComparator = new Comparator<IValueReference<IntType>>() {
        @Override
        public int compare(final IValueReference<IntType> g1,
                           final IValueReference<IntType> g2) {
          int v1 = IntTools.this.objectCodec.getIntegerValue(g1);
          int v2 = IntTools.this.objectCodec.getIntegerValue(g2);
          return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
        }
      };

      this.bufferedComparator = new Comparator<IValueReference<IntType>>() {
        @Override
        public int compare(final IValueReference<IntType> g1,
                           final IValueReference<IntType> g2) {
          // TODO (nbales) - Implement direct binary comparison
          int v1 = IntTools.this.bufferedCodec.getIntegerValue(g1);
          int v2 = IntTools.this.bufferedCodec.getIntegerValue(g1);
          return (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
        }
      };

      this.genericCopier = new IValueCopier<IntType>() {
        @Override
        public void copy(final IValueReference<IntType> valueSource,
                         final IValueReference<IntType> valueDestination) {
          IntTools.this.genericCodec.setIntegerValue(IntTools.this.genericCodec
              .getIntegerValue(valueSource), valueDestination);
        }
      };

      this.objectCopier = new IValueCopier<IntType>() {
        @Override
        public void copy(final IValueReference<IntType> valueSource,
                         final IValueReference<IntType> valueDestination) {
          IntTools.this.objectCodec.setIntegerValue(IntTools.this.objectCodec
              .getIntegerValue(valueSource), valueDestination);
        }
      };

      this.bufferedCopier = new IValueCopier<IntType>() {
        @Override
        public void copy(final IValueReference<IntType> valueSource,
                         final IValueReference<IntType> valueDestination) {
          BufferedIntegerReference bufferedSource =
              (BufferedIntegerReference) valueDestination;
          BufferedIntegerReference bufferedDestination =
              (BufferedIntegerReference) valueDestination;
          System.arraycopy(bufferedSource.buffer.array(), bufferedSource.length,
                           bufferedDestination.buffer.array(),
                           bufferedDestination.length, bufferedSource.length);
        }
      };

      this.genericCodec = new IntegerValueCodec() {
        @Override
        public int getIntegerValue(final IValueReference<IntType> valueSource) {
          int value = 0;
          if (valueSource instanceof BufferedIntegerReference) {
            BufferedIntegerReference buffered =
                (BufferedIntegerReference) valueSource;
            value = buffered.buffer.getInt(buffered.startOffset);
          } else if (valueSource instanceof ObjectIntegerReference) {
            value = ((ObjectIntegerReference) valueSource).value;
          } else {
            String message = "Encountered unanticipated value reference type ";
            message += valueSource.getClass().getName();
            throw new BassariskInternalError(message);
          }
          return value;
        }

        @Override
        public
            void
            setBufferedValueReference(final IValueReference<IntType> valueSource,
                                      final IBufferedValueReference<IntType> valueDestination) {
          IntTools.this.bufferedCodec.setIntegerValue(this
              .getIntegerValue(valueSource), valueDestination);
        }

        @Override
        public
            void
            setConcreteValueReference(final IValueReference<IntType> valueSource,
                                      final IObjectValueReference<IntType> valueDestination) {
          IntTools.this.objectCodec.setIntegerValue(this
              .getIntegerValue(valueSource), valueDestination);
        }

        @Override
        public void setIntegerValue(final int value,
                                    final IValueReference<IntType> valueDestination) {
          if (valueDestination instanceof BufferedIntegerReference) {
            BufferedIntegerReference buffered =
                (BufferedIntegerReference) valueDestination;
            buffered.buffer.putInt(buffered.startOffset, value);
          } else if (valueDestination instanceof ObjectIntegerReference) {
            ((ObjectIntegerReference) valueDestination).value = value;
          } else {
            String message = "Encountered unanticipated value reference type ";
            message += valueDestination.getClass().getName();
            throw new BassariskInternalError(message);
          }
        }
      };

      this.objectCodec = new IntegerValueCodec() {
        @Override
        public int getIntegerValue(final IValueReference<IntType> valueSource) {
          assert valueSource instanceof ObjectIntegerReference;
          BufferedIntegerReference buffered = (BufferedIntegerReference) valueSource;
          return buffered.buffer.getInt(buffered.startOffset);
        }

        @Override
        public
            void
            setBufferedValueReference(final IValueReference<IntType> valueSource,
                                      final IBufferedValueReference<IntType> valueDestination) {
          IntTools.this.bufferedCodec.setIntegerValue(this
              .getIntegerValue(valueSource), valueDestination);
        }

        @Override
        public
            void
            setConcreteValueReference(final IValueReference<IntType> valueSource,
                                      final IObjectValueReference<IntType> valueDestination) {
          IntTools.this.objectCodec.setIntegerValue(this
              .getIntegerValue(valueSource), valueDestination);
        }

        @Override
        public void setIntegerValue(final int value,
                                    final IValueReference<IntType> valueDestination) {
          assert valueDestination instanceof ObjectIntegerReference;
          ((ObjectIntegerReference) valueDestination).value = value;
        }
      };

      this.bufferedCodec = new IntegerValueCodec() {
        @Override
        public int getIntegerValue(final IValueReference<IntType> valueSource) {
          assert valueSource instanceof BufferedIntegerReference;
          return ((ObjectIntegerReference) valueSource).value;
        }

        @Override
        public
            void
            setBufferedValueReference(final IValueReference<IntType> valueSource,
                                      final IBufferedValueReference<IntType> valueDestination) {
          IntTools.this.bufferedCodec.setIntegerValue(this
              .getIntegerValue(valueSource), valueDestination);
        }

        @Override
        public
            void
            setConcreteValueReference(final IValueReference<IntType> valueSource,
                                      final IObjectValueReference<IntType> valueDestination) {
          IntTools.this.objectCodec.setIntegerValue(this
              .getIntegerValue(valueSource), valueDestination);
        }

        @Override
        public void setIntegerValue(final int value,
                                    final IValueReference<IntType> valueDestination) {
          assert valueDestination instanceof BufferedIntegerReference;
          BufferedIntegerReference buffered =
              (BufferedIntegerReference) valueDestination;
          buffered.buffer.putInt(buffered.startOffset, value);
        }
      };
    };

    @Override
    public IntegerValueCodec getFastCodec(final IReferenceSort refType) {
      ReferenceSortOption option = refType.getSortOption();
      switch (option) {
      case JAVA_OBJECT:
        return this.objectCodec;
      case BYTE_BUFFER_SERIALIZED:
        return this.bufferedCodec;
      default:
        String message =
            String.format("Using generic int codec when fast comparator requested. "
                + "Potential to increase performance by implementing fast int "
                + "codec for %s", option.toString());
        LOG.log(Level.INFO, message);
        return this.genericCodec;
      }
    }

    @Override
    public Comparator<IValueReference<IntType>>
        getFastComparator(final IReferenceSort leftRefType,
                          final IReferenceSort rightRefType) {
      ReferenceSortOption leftOption = leftRefType.getSortOption();
      ReferenceSortOption rightOption = rightRefType.getSortOption();
      if (leftOption == ReferenceSortOption.JAVA_OBJECT
          && rightOption == ReferenceSortOption.JAVA_OBJECT) {
        return this.objectComparator;
      } else if (leftOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED
          && rightOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED) {
        return this.bufferedComparator;
      } else {
        String message =
            String
                .format("Using generic int Comparator when fast comparator requested. "
                            + "Potential to increase performance by implementing fast int "
                            + "comparator for %s and %s", leftOption.toString(),
                        rightRefType.toString());
        LOG.log(Level.INFO, message);
        return this.genericComparator;
      }
    }

    @Override
    public IValueCopier<IntType> getFastCopier(final IReferenceSort leftRefType,
                                               final IReferenceSort rightRefType) {
      ReferenceSortOption leftOption = leftRefType.getSortOption();
      ReferenceSortOption rightOption = rightRefType.getSortOption();
      if (leftOption == ReferenceSortOption.JAVA_OBJECT
          && rightOption == ReferenceSortOption.JAVA_OBJECT) {
        return this.objectCopier;
      } else if (leftOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED
          && rightOption == ReferenceSortOption.BYTE_BUFFER_SERIALIZED) {
        return this.bufferedCopier;
      } else {
        String message =
            String
                .format("Using generic int Copier when fast comparator requested. "
                            + "Potential to increase performance by implementing fast int "
                            + "copier for %s and %s", leftOption.toString(),
                        rightRefType.toString());
        LOG.log(Level.INFO, message);
        return this.genericCopier;
      }
    }

    @Override
    public IntegerValueCodec getGenericCodec() {
      return this.genericCodec;
    }

    @Override
    public Comparator<IValueReference<IntType>> getGenericComparator() {
      return this.genericComparator;
    }

    @Override
    public IValueCopier<IntType> getGenericCopyer() {
      return this.genericCopier;
    }

    @Override
    public BufferedIntegerReference newBufferedReference() {
      return new BufferedIntegerReference();
    }

    @Override
    public ObjectIntegerReference newReference() {
      return new ObjectIntegerReference();
    }
  }

  public static class ObjectIntegerReference implements
      IObjectValueReference<IntType> {
    private int value;

    private ObjectIntegerReference() {
      this.value = 0;
    }

    public ObjectIntegerReference(final int value) {
      this.value = value;
    }

    @Override
    public IValueType getType() {
      return IntType.INSTANCE;
    }

    public int getValue() {
      return this.value;
    }

    public int increment() {
      return ++this.value;
    };

    @Override
    public void resetDefault() {
      this.value = 0;
    }

    public void setValue(final int newValue) {
      this.value = newValue;
    }

    @Override
    public String toString() {
      return "Object 32bit Integer: " + this.value;
    }
  }

  public static final IntType INSTANCE = new IntType();

  private final static Logger LOG = Logger.getLogger(IntType.class.getName());

  private IntType() {}

  @Override
  public long getUid() {
    return serialVersionUID;
  }
}
